#include <sys/time.h>
#include <cassert>
#include "ALooper.h"
#include "AHandler.h"
#include "ALooperRoster.h"
#include "AMessage.h"
#include "memory"
#include "thread"

extern "C"{
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
#include "freertos/event_groups.h"
}

ALooperRoster gLooperRoster;

struct ALooper::LooperThread{
public:
    explicit LooperThread(ALooper *looper):mLooper(looper) {
    }

    int32_t run(const std::string &name) {
        mFlag = true;
        xTaskCreate(threadLoop, name.c_str(), 16384, this, 1, &mTaskHandler);
        return 0;
    }


    static void threadLoop(void *ctx) {
        auto thiz = (LooperThread *)ctx;
        while (thiz->mFlag){
            bool ret = thiz->mLooper->loop();
            if( !ret ){
                break;
            }
        }
    }

    void requestExit(){
        mFlag = false;
        if( mTaskHandler ){
            vTaskDelete(mTaskHandler);
        }
    }

    virtual ~LooperThread() {
        mFlag = false;

    }

private:
    bool mFlag = false;
    ALooper *mLooper = nullptr;
    TaskHandle_t mTaskHandler = nullptr;
};



// static
int64_t ALooper::GetNowUs() {
    auto now = std::chrono::system_clock::now().time_since_epoch();
    return std::chrono::duration_cast<std::chrono::milliseconds>(now).count();
}

ALooper::ALooper() {
    // clean up stale AHandlers. Doing it here instead of in the destructor avoids
    // the side effect of objects being deleted from the unregister function recursively.
    gLooperRoster.unregisterStaleHandlers();
    start();
}

ALooper::~ALooper() {
    stop();
    // stale AHandlers are now cleaned up in the constructor of the next ALooper to come along
}

void ALooper::setName(const char *name) {
    mName = name;
}

ALooper::handler_id ALooper::registerHandler(const std::shared_ptr<AHandler> &handler) {
    return gLooperRoster.registerHandler(shared_from_this(), handler);
}

void ALooper::unregisterHandler(handler_id handlerID) {
    gLooperRoster.unregisterHandler(handlerID);
}

int32_t ALooper::start() {
    std::lock_guard<std::mutex> autoLock(mLock);

    if (mThread != nullptr) {
        return INVALID_OPERATION;
    }

    mThread = std::make_shared<LooperThread>(this);

    int32_t err = mThread->run(
            mName.empty() ? "ALooper" : mName.c_str());
    return err;
}

int32_t ALooper::stop() {
    if (mThread == nullptr) {
        return INVALID_OPERATION;
    }

    mThread->requestExit();

    mQueueChangedCondition.notify_one();
    {
        std::lock_guard<std::mutex> autoLock(mRepliesLock);
        mRepliesCondition.notify_all();
    }


    return NO_ERROR;
}

void ALooper::post(const std::shared_ptr<AMessage> &msg, int64_t delayUs) {
    std::lock_guard<std::mutex> autoLock(mLock);

    int64_t whenUs;
    if (delayUs > 0) {
        int64_t nowUs = GetNowUs();
        whenUs = (delayUs > INT64_MAX - nowUs ? INT64_MAX : nowUs + delayUs);

    } else {
        whenUs = GetNowUs();
    }

    auto it = mEventQueue.begin();
    while (it != mEventQueue.end() && (*it).mWhenUs <= whenUs) {
        ++it;
    }

    Event event;
    event.mWhenUs = whenUs;
    event.mMessage = msg;

    if (it == mEventQueue.begin()) {
        mQueueChangedCondition.notify_one();
    }

    mEventQueue.insert(it, event);
}

bool ALooper::loop() {
    Event event;
    {
        std::unique_lock<std::mutex> autoLock(mLock);
        if (mThread == nullptr) {
            return false;
        }
        if (mEventQueue.empty()) {
            mQueueChangedCondition.wait(autoLock);
            return true;
        }
        int64_t whenUs = (mEventQueue.front()).mWhenUs;
        int64_t nowUs = GetNowUs();
        if (whenUs > nowUs) {
            int64_t delayUs = whenUs - nowUs;
            if (delayUs > INT64_MAX / 1000) {
                delayUs = INT64_MAX / 1000;
            }
            auto delay = std::chrono::duration<int32_t, std::milli>(delayUs);
            mQueueChangedCondition.wait_for(autoLock, delay);

            return true;
        }
        event = mEventQueue.front();
        mEventQueue.pop_front();
    }

    event.mMessage->deliver();
    // NOTE: It's important to note that at this point our "ALooper" object
    // may no longer exist (its final reference may have gone away while
    // delivering the message). We have made sure, however, that loop()
    // won't be called again.

    return true;
}

// to be called by AMessage::postAndAwaitResponse only
std::shared_ptr<AReplyToken> ALooper::createReplyToken() {
    return std::make_shared<AReplyToken>(shared_from_this());
}

// to be called by AMessage::postAndAwaitResponse only
int32_t ALooper::awaitResponse(const std::shared_ptr<AReplyToken> &replyToken,
                               std::shared_ptr<AMessage> &response) {
    // return status in case we want to handle an interrupted wait
    std::unique_lock<std::mutex> repliesLock(mRepliesLock);
    assert(replyToken != nullptr);
    while (!replyToken->retrieveReply(response)) {
        {
            std::lock_guard<std::mutex> autoLock(mLock);
            if (mThread == nullptr) {
                return -ENOENT;
            }
        }
        mRepliesCondition.wait(repliesLock);
    }
    return NO_ERROR;
}

int32_t ALooper::postReply(const std::shared_ptr<AReplyToken> &replyToken, const std::shared_ptr<AMessage> &reply) {
    std::lock_guard<std::mutex> autoLock(mRepliesLock);
    int32_t err = replyToken->setReply(reply);
    if (err == NO_ERROR) {
        mRepliesCondition.notify_all();
    }
    return err;
}
